﻿<!DOCTYPE html>
<html lang="zh-cmn-Hans">
  <head>
    <meta charset="utf-8" />
    <title>简单的绳状物理学</title>

    <style>
      html {
        overflow: hidden;
        touch-action: none;
        content-zooming: none;
        font-family: sans-serif;
        font-size: 12px;
      }
      body {
        position: absolute;
        margin: 0;
        padding: 0;
        background: #222;
        width: 100%;
        height: 100%;
        text-align: center;
        color: #fff;
      }
      #canvas {
        /*   position: absolute;
  top: 0; right: 0; left: 0;
  margin: auto; */

        margin: auto;
        margin-bottom: 1em;
        max-width: 100%;
        height: auto;
        /* 	background:#000; */
      }
    </style>
  </head>
  <body>
    <canvas id="canvas" width="900" height="500"></canvas>

    <script>
      'use strict';

      var _createClass = (function() {
        function defineProperties(target, props) {
          for (var i = 0; i < props.length; i++) {
            var descriptor = props[i];
            descriptor.enumerable = descriptor.enumerable || false;
            descriptor.configurable = true;
            if ('value' in descriptor) descriptor.writable = true;
            Object.defineProperty(target, descriptor.key, descriptor);
          }
        }
        return function(Constructor, protoProps, staticProps) {
          if (protoProps) defineProperties(Constructor.prototype, protoProps);
          if (staticProps) defineProperties(Constructor, staticProps);
          return Constructor;
        };
      })();

      function _possibleConstructorReturn(self, call) {
        if (!self) {
          throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
        }
        return call && (typeof call === 'object' || typeof call === 'function') ? call : self;
      }

      function _inherits(subClass, superClass) {
        if (typeof superClass !== 'function' && superClass !== null) {
          throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass);
        }
        subClass.prototype = Object.create(superClass && superClass.prototype, {
          constructor: { value: subClass, enumerable: false, writable: true, configurable: true },
        });
        if (superClass)
          Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : (subClass.__proto__ = superClass);
      }

      function _classCallCheck(instance, Constructor) {
        if (!(instance instanceof Constructor)) {
          throw new TypeError('Cannot call a class as a function');
        }
      }

      console.clear();

      var TWOPI = Math.PI * 2;

      function distance(x1, y1, x2, y2) {
        var dx = x1 - x2;
        var dy = y1 - y2;
        return Math.sqrt(dx * dx + dy * dy);
      }

      var gravity = 0.5;

      // VNode class

      var VNode = (function() {
        function VNode(node) {
          _classCallCheck(this, VNode);

          this.x = node.x || 0;
          this.y = node.y || 0;
          this.oldX = this.x;
          this.oldY = this.y;
          this.w = node.w || 2;
          this.angle = node.angle || 0;
          this.gravity = node.gravity || gravity;
          this.mass = node.mass || 1.0;

          this.color = node.color;
          this.letter = node.letter;

          this.pointerMove = node.pointerMove;
          this.fixed = node.fixed;
        }
        // verlet integration

        _createClass(VNode, [
          {
            key: 'integrate',
            value: function integrate(pointer) {
              if (this.lock && (!this.lockX || !this.lockY)) {
                this.lockX = this.x;
                this.lockY = this.y;
              }

              if (this.pointerMove && pointer && distance(this.x, this.y, pointer.x, pointer.y) < this.w + pointer.w) {
                this.x += (pointer.x - this.x) / (this.mass * 18);
                this.y += (pointer.y - this.y) / (this.mass * 18);
              } else if (this.lock) {
                this.x += (this.lockX - this.x) * this.lock;
                this.y += (this.lockY - this.y) * this.lock;
              }

              if (!this.fixed) {
                var x = this.x;
                var y = this.y;
                this.x += this.x - this.oldX;
                this.y += this.y - this.oldY + this.gravity;
                this.oldX = x;
                this.oldY = y;
              }
            },
          },
          {
            key: 'set',
            value: function set(x, y) {
              this.oldX = this.x = x;
              this.oldY = this.y = y;
            },
            // draw node
          },
          {
            key: 'draw',
            value: function draw(ctx) {
              if (!this.color) {
                return;
              }
              // ctx.globalAlpha = 0.8;
              ctx.translate(this.x, this.y);
              ctx.rotate(this.angle);
              ctx.fillStyle = this.color;

              ctx.beginPath();

              if (this.letter) {
                ctx.globalAlpha = 1;
                ctx.rotate(Math.PI / 2);

                ctx.rect(-7, 0, 14, 1);

                if (this.letter.length === 1) {
                  // 文字
                  ctx.textAlign = 'center';
                  ctx.textBaseline = 'middle';
                  ctx.font = '75px "Bebas Neue", monospace';
                  ctx.fillStyle = this.color;
                  ctx.fillText(this.letter, 0, this.w * 0.25);
                } else {
                  // 图片
                  var img = new Image();
                  img.src = this.letter;
                  ctx.drawImage(img, -40, 0);
                }
              } else {
                ctx.globalAlpha = 0.2;
                ctx.rect(-this.w, -this.w, this.w * 2, this.w * 2);
                // ctx.arc(this.x, this.y, this.w, 0, 2 * Math.PI);
              }
              ctx.closePath();
              ctx.fill();
              ctx.setTransform(1, 0, 0, 1, 0, 0);
            },
          },
        ]);

        return VNode;
      })();
      // constraint class

      var Constraint = (function() {
        function Constraint(n0, n1, stiffness) {
          _classCallCheck(this, Constraint);

          this.n0 = n0;
          this.n1 = n1;
          this.dist = distance(n0.x, n0.y, n1.x, n1.y);
          this.stiffness = stiffness || 0.5;
          this.firstRun = true;
        }
        // solve constraint

        _createClass(Constraint, [
          {
            key: 'solve',
            value: function solve() {
              var dx = this.n0.x - this.n1.x;
              var dy = this.n0.y - this.n1.y;

              var newAngle = Math.atan2(dy, dx);
              this.n1.angle = newAngle;

              var currentDist = distance(this.n0.x, this.n0.y, this.n1.x, this.n1.y);
              var delta = (this.stiffness * (currentDist - this.dist)) / currentDist;
              dx *= delta;
              dy *= delta;

              if (this.firstRun) {
                this.firstRun = false;
                if (!this.n1.fixed) {
                  this.n1.x += dx;
                  this.n1.y += dy;
                }
                if (!this.n0.fixed) {
                  this.n0.x -= dx;
                  this.n0.y -= dy;
                }
                return;
              }

              var m1 = this.n0.mass + this.n1.mass;
              var m2 = this.n0.mass / m1;
              m1 = this.n1.mass / m1;

              if (!this.n1.fixed) {
                this.n1.x += dx * m2;
                this.n1.y += dy * m2;
              }
              if (!this.n0.fixed) {
                this.n0.x -= dx * m1;
                this.n0.y -= dy * m1;
              }
            },
            // draw constraint
          },
          {
            key: 'draw',
            value: function draw(ctx) {
              ctx.globalAlpha = 0.9;
              ctx.beginPath();
              ctx.moveTo(this.n0.x, this.n0.y);
              ctx.lineTo(this.n1.x, this.n1.y);
              ctx.strokeStyle = '#fff';
              ctx.stroke();
            },
          },
        ]);

        return Constraint;
      })();

      var Rope = (function() {
        function Rope(rope) {
          _classCallCheck(this, Rope);

          var x = rope.x,
            y = rope.y,
            length = rope.length,
            points = rope.points,
            vertical = rope.vertical,
            fixedEnds = rope.fixedEnds,
            startNode = rope.startNode,
            letter = rope.letter,
            endNode = rope.endNode,
            stiffness = rope.stiffness,
            constrain = rope.constrain,
            gravity = rope.gravity,
            pointerMove = rope.pointerMove;

          this.stiffness = stiffness || 1;
          this.nodes = [];
          this.constraints = [];
          if (letter === ' ') {
            return this;
          }

          var dist = length / points;

          for (var i = 0, _last = points - 1; i < points; i++) {
            var size = letter && i === _last ? 15 : 2;
            var spacing = dist * i + size;
            var node = new VNode({
              w: size,
              mass: 0.1, //(i === last ? .5 : .1),
              fixed: fixedEnds && (i === 0 || i === _last),
            });

            node = (i === 0 && startNode) || (i === _last && endNode) || node;

            node.gravity = gravity;
            //node.pointerMove = pointerMove;

            if (i === _last && letter) {
              node.letter = letter;
              node.color = '#FFF';
              node.pointerMove = true;
            }

            node.oldX = node.x = x + (!vertical ? spacing : 0);
            node.oldY = node.y = y + (vertical ? spacing : 0);

            this.nodes.push(node);
          }

          constrain ? this.makeConstraints() : null;
        }

        _createClass(Rope, [
          {
            key: 'makeConstraints',
            value: function makeConstraints() {
              for (var i = 1; i < this.nodes.length; i++) {
                this.constraints.push(new Constraint(this.nodes[i - 1], this.nodes[i], this.stiffness));
              }
            },
          },
          {
            key: 'run',
            value: function run(pointer) {
              // integration
              var _iteratorNormalCompletion = true;
              var _didIteratorError = false;
              var _iteratorError = undefined;

              try {
                for (
                  var _iterator = this.nodes[Symbol.iterator](), _step;
                  !(_iteratorNormalCompletion = (_step = _iterator.next()).done);
                  _iteratorNormalCompletion = true
                ) {
                  var n = _step.value;

                  n.integrate(pointer);
                }
                // solve constraints
              } catch (err) {
                _didIteratorError = true;
                _iteratorError = err;
              } finally {
                try {
                  if (!_iteratorNormalCompletion && _iterator.return) {
                    _iterator.return();
                  }
                } finally {
                  if (_didIteratorError) {
                    throw _iteratorError;
                  }
                }
              }

              for (var i = 0; i < 5; i++) {
                var _iteratorNormalCompletion2 = true;
                var _didIteratorError2 = false;
                var _iteratorError2 = undefined;

                try {
                  for (
                    var _iterator2 = this.constraints[Symbol.iterator](), _step2;
                    !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done);
                    _iteratorNormalCompletion2 = true
                  ) {
                    var _n = _step2.value;

                    _n.solve();
                  }
                } catch (err) {
                  _didIteratorError2 = true;
                  _iteratorError2 = err;
                } finally {
                  try {
                    if (!_iteratorNormalCompletion2 && _iterator2.return) {
                      _iterator2.return();
                    }
                  } finally {
                    if (_didIteratorError2) {
                      throw _iteratorError2;
                    }
                  }
                }
              }
            },

            // draw(ctx) {
            //   // draw constraints
            //   this.constraints.forEach(n => {
            //     n.draw(ctx);
            //   });
            //   // draw nodes
            //   this.nodes.forEach(n => {
            //     n.draw(ctx);
            //   })
            // }
          },
          {
            key: 'draw',
            value: function draw(ctx) {
              var vertices = Array.from(this.constraints).reduce(function(p, c, i, a) {
                p.push(c.n0);
                if (i == a.length - 1) p.push(c.n1);
                return p;
              }, []);

              var h = function h(x, y) {
                return Math.sqrt(x * x + y * y);
              };
              var tension = 0.5;

              if (!vertices.length) return;

              var controls = vertices.map(function() {
                return null;
              });
              for (var i = 1; i < vertices.length - 1; ++i) {
                var previous = vertices[i - 1];
                var current = vertices[i];
                var next = vertices[i + 1];

                var rdx = next.x - previous.x,
                  rdy = next.y - previous.y,
                  rd = h(rdx, rdy),
                  dx = rdx / rd,
                  dy = rdy / rd;

                var dp = h(current.x - previous.x, current.y - previous.y),
                  dn = h(current.x - next.x, current.y - next.y);

                var cpx = current.x - dx * dp * tension,
                  cpy = current.y - dy * dp * tension,
                  cnx = current.x + dx * dn * tension,
                  cny = current.y + dy * dn * tension;

                controls[i] = {
                  cp: {
                    x: cpx,
                    y: cpy,
                  },
                  cn: {
                    x: cnx,
                    y: cny,
                  },
                };
              }

              controls[0] = {
                cn: {
                  x: (vertices[0].x + controls[1].cp.x) / 2,
                  y: (vertices[0].y + controls[1].cp.y) / 2,
                },
              };

              controls[vertices.length - 1] = {
                cp: {
                  x: (vertices[vertices.length - 1].x + controls[vertices.length - 2].cn.x) / 2,
                  y: (vertices[vertices.length - 1].y + controls[vertices.length - 2].cn.y) / 2,
                },
              };

              // Draw vertices & control points
              // ctx.fillStyle = 'blue';
              // ctx.fillRect(vertices[0].x, vertices[0].y, 4, 4);
              // for (let i = 1; i < vertices.length; ++i)
              // {
              // 	const v = vertices[i];
              // 	const ca = controls[i - 1];
              // 	const cb = controls[i];
              // 	ctx.fillStyle = 'blue';
              // 	ctx.fillRect(v.x, v.y, 4, 4);
              // 	ctx.fillStyle = 'green';
              // 	ctx.fillRect(ca.cn.x, ca.cn.y, 4, 4);
              // 	ctx.fillRect(cb.cp.x, cb.cp.y, 4, 4);
              // }

              ctx.globalAlpha = 0.9;
              ctx.beginPath();
              ctx.moveTo(vertices[0].x, vertices[0].y);
              for (var _i = 1; _i < vertices.length; ++_i) {
                var v = vertices[_i];
                var ca = controls[_i - 1];
                var cb = controls[_i];

                ctx.bezierCurveTo(ca.cn.x, ca.cn.y, cb.cp.x, cb.cp.y, v.x, v.y);
              }
              ctx.strokeStyle = 'white';
              ctx.stroke();
              ctx.closePath();

              // draw nodes
              this.nodes.forEach(function(n) {
                n.draw(ctx);
              });
            },
          },
        ]);

        return Rope;
      })();

      // Pointer class

      var Pointer = (function(_VNode) {
        _inherits(Pointer, _VNode);

        function Pointer(canvas) {
          _classCallCheck(this, Pointer);

          var _this = _possibleConstructorReturn(
            this,
            (Pointer.__proto__ || Object.getPrototypeOf(Pointer)).call(this, {
              x: 0,
              y: 0,
              w: 8,
              color: '#F00',
              fixed: true,
            })
          );

          _this.elem = canvas;
          canvas.addEventListener(
            'mousemove',
            function(e) {
              return _this.move(e);
            },
            false
          );
          canvas.addEventListener(
            'touchmove',
            function(e) {
              return _this.move(e);
            },
            false
          );
          return _this;
        }

        _createClass(Pointer, [
          {
            key: 'move',
            value: function move(e) {
              var touchMode = e.targetTouches;
              var pointer = e;
              if (touchMode) {
                e.preventDefault();
                pointer = touchMode[0];
              }
              var rect = this.elem.getBoundingClientRect();
              var cw = this.elem.width;
              var ch = this.elem.height;

              // get the scale based on actual width;
              var sx = cw / this.elem.offsetWidth;
              var sy = ch / this.elem.offsetHeight;

              this.x = (pointer.clientX - rect.left) * sx;
              this.y = (pointer.clientY - rect.top) * sy;
            },
          },
        ]);

        return Pointer;
      })(VNode);

      var Scene = (function() {
        function Scene(canvas) {
          _classCallCheck(this, Scene);

          this.draw = true;
          this.canvas = canvas;
          this.ctx = canvas.getContext('2d');

          this.nodes = new Set();
          this.constraints = new Set();
          this.ropes = [];

          this.pointer = new Pointer(canvas);
          this.nodes.add(this.pointer);

          this.run = this.run.bind(this);
          this.addRope = this.addRope.bind(this);
          this.add = this.add.bind(this);
        }

        // animation loop

        _createClass(Scene, [
          {
            key: 'run',
            value: function run() {
              var _this2 = this;

              // if (!canvas.isConnected) {
              //   return;
              // }
              requestAnimationFrame(this.run);
              // clear screen
              this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

              this.ropes.forEach(function(rope) {
                rope.run(_this2.pointer);
              });

              this.ropes.forEach(function(rope) {
                rope.draw(_this2.ctx);
              });

              // // integration
              // for (let n of nodes) {
              //   n.integrate(pointer);
              // }
              // solve constraints
              // for (let i = 0; i < 4; i++) {
              //   for (let n of constraints) {
              //     n.solve();
              //   }
              // }
              // // draw constraints
              // for (let n of constraints) {
              //   n.draw(ctx);
              // }
              // draw nodes
              var _iteratorNormalCompletion3 = true;
              var _didIteratorError3 = false;
              var _iteratorError3 = undefined;

              try {
                for (
                  var _iterator3 = this.nodes[Symbol.iterator](), _step3;
                  !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done);
                  _iteratorNormalCompletion3 = true
                ) {
                  var n = _step3.value;

                  n.draw(this.ctx);
                }
              } catch (err) {
                _didIteratorError3 = true;
                _iteratorError3 = err;
              } finally {
                try {
                  if (!_iteratorNormalCompletion3 && _iterator3.return) {
                    _iterator3.return();
                  }
                } finally {
                  if (_didIteratorError3) {
                    throw _iteratorError3;
                  }
                }
              }
            },
          },
          {
            key: 'addRope',
            value: function addRope(rope) {
              this.ropes.push(rope);
            },
          },
          {
            key: 'add',
            value: function add(struct) {
              // load nodes
              for (var n in struct.nodes) {
                this.nodes.add(struct.nodes[n]);
                /*
       const node = new Node(struct.nodes[n]);
       struct.nodes[n].id = node;
       nodes.add(node);
       */
              }

              // load constraints
              for (var i = 0; i < struct.constraints.length; i++) {
                var c = struct.constraints[i];
                this.constraints.add(c);
                /*
       	new Constraint(
       		struct.nodes[c[0]].id,
       		struct.nodes[c[1]].id
       	)
       );
       */
              }
            },
          },
        ]);

        return Scene;
      })();

      var scene = new Scene(document.querySelector('#canvas'));

      scene.run();

      // const pointer = new Pointer(canvas);

      //   var phrase = ' I LOVE YOU ';
      var phrase = ['', './image/a.png', '臭', './image/b.png', '傻', './image/a.png', '', '比', './image/a.png', ''];

      var r = new Rope({
        x: scene.canvas.width * 0.15,
        y: 40,
        length: scene.canvas.width * 0.7,
        points: phrase.length,
        vertical: false,
        dangleEnd: false,
        fixedEnds: true,
        stiffness: 1.5,
        constrain: false,
        gravity: 0.1,
      });

      var center = r.nodes.length / 2;

      var ropes = r.nodes.map(function(n, i) {
        n.set(n.x, 60 + 80 * (1 - Math.abs(((center - i) % center) / center)));

        if (phrase[i] !== '' && phrase[i] !== ' ') {
          //if ( i !== 0 && i !== r.nodes.length - 1 ) {
          return new Rope({
            startNode: n,
            x: n.x,
            y: n.y,
            length: 60,
            points: 4,
            letter: phrase[i],
            vertical: true,
            stiffness: 1, //2.5,,
            constrain: false,
            gravity: 0.5,
          });
        }

        //}
      });
      var first = r.nodes[0];
      var last = r.nodes[r.nodes.length - 1];

      first.set(2, -2);
      last.set(scene.canvas.width - 2, -2);

      r.makeConstraints();

      ropes = ropes;
      scene.addRope(r);
      ropes
        .filter(function(r) {
          return r;
        })
        .forEach(function(r) {
          r.makeConstraints();
          scene.addRope(r);
        });
    </script>
  </body>
</html>
